/***************************************************************************
 *   Copyright (C) 2012                                                    *
 *   Anatole Duprat <anatole.duprat@gmail.com>                             *
 *   Charles Bulckaen  <xtrium@frequency.fr>                               *
 *                                                                         *
 *   This program is free software; you can redistribute it and/or         *
 *   modify it under the terms of the GNU General Public License           *
 *   as published by the Free Software Foundation; either version 2        *
 *   of the License, or (at your option) any later version.                *
 *                                                                         *
 *   This program is distributed in the hope that it will be useful,       *
 *   but WITHOUT ANY WARRANTY; without even the implied warranty of        *
 *   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the         *
 *   GNU General Public License for more details.                          *
 *                                                                         *
 *   You should have received a copy of the GNU General Public License     *
 *   along with this program; if not, write to the                         *
 *   Free Software Foundation, Inc.,                                       *
 *   51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA          *
 ***************************************************************************/



#include "renderwidget.hh"
#include "glextensions.hh"
#include "fast2dquad.hh"
#include "shader.hh"
#include "fbo.hh"

#include <QtCore>
#include <GL/glu.h>

#include "sceneeditortreeitem.hh"


RenderWidget::RenderWidget(QWidget* parent) : QGLWidget(parent)
{
    _trigX = 0.707f;
    _trigY = 1.309f;
    _ofsX = _ofsY = _ofsZ = 0.0f;
    _zoomFactor = 5.0f;

    _shiftPressed = false;
    _moveToChangeOffset = false;
    _fbo = NULL;

    _demoTime = 0.0f;

    _cameraType = 0;

    _freeflyEnabled = false;
    _freeflySpeed = 0.0f;
    _freeflyPosition = QVector3D(2.0f, 2.0f, 2.0f);
    _freeflyTrigX = 0.0f; _freeflyTrigY = 2.0f;
    _freeflyPxOffset = _freeflyPyOffset = 0.0f;

    _scene.camPosX=.0f;
    _scene.camPosY=.0f;
    _scene.camPosZ=1.0f;
    _scene.camTargetX=.0f;
    _scene.camTargetY=.0f;

    _sceneTree = new SceneEditorTreeItem(QString(), NULL);
    _sceneTree->appendChild(new SceneEditorTreeItem(QString("Camera"), NULL));
      _sceneTree->child("Camera")->appendChild(new SceneEditorTreeItem(QString("Position"), NULL));
        _sceneTree->child("Camera.Position")->appendChild(new SceneEditorTreeItem(QString("X"), &(_scene.camPosX), 2.0f));
        _sceneTree->child("Camera.Position")->appendChild(new SceneEditorTreeItem(QString("Y"), &(_scene.camPosY), 2.0f));
        _sceneTree->child("Camera.Position")->appendChild(new SceneEditorTreeItem(QString("Z"), &(_scene.camPosZ), 2.0f));
      _sceneTree->child("Camera")->appendChild(new SceneEditorTreeItem(QString("Target"), NULL));
        _sceneTree->child("Camera.Target")->appendChild(new SceneEditorTreeItem(QString("X"), &(_scene.camTargetX)));
        _sceneTree->child("Camera.Target")->appendChild(new SceneEditorTreeItem(QString("Y"), &(_scene.camTargetY)));
        _sceneTree->child("Camera.Target")->appendChild(new SceneEditorTreeItem(QString("Z"), &(_scene.camTargetZ)));
        _sceneTree->child("Camera")->appendChild(new SceneEditorTreeItem(QString("Fov"), &(_scene.fov), 1.5f));

        _sceneTree->appendChild(new SceneEditorTreeItem(QString("Scene"), NULL));
          for(int i=0; i<8; i++)
          {
              QString name = "Param " + QString::number(i);
              _sceneTree->child("Scene")->appendChild(new SceneEditorTreeItem(name, &(_scene.ShaderParam[i]), .0f));
          }
        _sceneTree->appendChild(new SceneEditorTreeItem(QString("Post Processing"), NULL));
          for(int i=0; i<8; i++)
          {
              QString name = "Param " + QString::number(i);
              _sceneTree->child("Post Processing")->appendChild(new SceneEditorTreeItem(name, &(_scene.PostParam[i]), .0f));
          }


}

SceneEditorTreeItem* RenderWidget::sceneTree() const
{
    return _sceneTree;
}


static unsigned int mirand = 1;

static float sfrand( void )
{
    unsigned int a;

    mirand *= 16807;

    a = (mirand&0x007fffff) | 0x40000000;

    return( *((float*)&a) - 3.0f );
}
/*
static const char vertexPerlinShader[] ="varying vec3 v;"
"void main()"
"{"
    "gl_Position = gl_Vertex;"
    "v= gl_MultiTexCoord0.xyz;"
"}";

static const char pixelPerlinShader[] = "varying vec3 v;"
"uniform sampler3D RandTex3D;"
"uniform float step;"
"vec4 Rand3f(const in vec3 p)"
"{"
    "return texture3D( RandTex3D, p );"
"}"
"float PerlinNoise3f(in vec3 p, in int octave, in float persistance)"
"{"
    "float color = 0.0;"
    "for(int i=1; i<octave+1; i++)"
        "color += Rand3f( p * pow( 2.0, float(i) ) ).r * pow(persistance,float(i));"

    "return color;"
"}"
"void main()"
"{"
    "gl_FragColor = vec4(  PerlinNoise3f( v*.2+vec3(.0,.0,step), 10, 0.5 )  );"
"}";

void RenderWidget::InitPerlinNoise()
{
    //Make full noise texture
    glGenTextures(1,&_noiseTexture);
    glEnable(GL_TEXTURE_3D);
    glBindTexture(GL_TEXTURE_3D, _noiseTexture);
    glTexParameteri(GL_TEXTURE_3D, GL_GENERATE_MIPMAP, GL_TRUE);
    glTexParameteri(GL_TEXTURE_3D,GL_TEXTURE_MIN_FILTER,GL_LINEAR);
    glTexParameteri(GL_TEXTURE_3D,GL_TEXTURE_MAG_FILTER,GL_LINEAR_MIPMAP_LINEAR);
    unsigned char *Data = new unsigned char[128*128*128];
    for(int i=0; i<64*64*64; i++)
    {
                Data[i] = (unsigned char)((sfrand()*.5f+.5f)*255.f);
    }
    glTexImage3D(GL_TEXTURE_3D, 0, GL_LUMINANCE, 64,64,64, 0, GL_LUMINANCE, GL_UNSIGNED_BYTE, Data);


    //Make shader for 3D Perlin noise precalc ...
    int error;
    GLuint Shader = ShaderCompil(vertexPerlinShader,pixelPerlinShader, &error);


    //Back 3D Perlin ..
    glViewport(0,0,128,128);
    ShaderUse(Shader);

    unsigned char tmpData[128*128];
    for(int i=0; i<128; i++)
    {
        ShaderSendf(Shader, "step", (float(i))/128.f );
        Fast2DQuadDraw();
        glFlush();
        glFinish();
        glReadPixels(0,0,128,128, GL_RED,  GL_UNSIGNED_BYTE,  tmpData);
        for(int j=0; j<128*128; j++)
            Data[i*128*128+j] = tmpData[j];
    }
    ShaderUse(0);

    //Update the 3D texture
    glTexImage3D(GL_TEXTURE_3D, 0, GL_LUMINANCE, 128,128,128, 0, GL_LUMINANCE, GL_UNSIGNED_BYTE, Data);

    //And free data =)
    delete[] Data;
}
*/


GLuint RenderWidget::getTexture(int id)
{
    return _texture[id];
}

void RenderWidget::initializeGL() {
    glEnable(GL_TEXTURE_2D);
    glMatrixMode(GL_MODELVIEW);
    glLoadIdentity();
    InitProcAdresses(this->context());
    Fast2DQuadInit();
    _fbo = new FBO;

    glGenTextures(4,_texture);

}

void RenderWidget::CompilShader(QString vertexShader, QString fragmentShader)
{
    int error;
    _shader = ShaderCompil(vertexShader.toStdString().c_str(),fragmentShader.toStdString().c_str(), &error);
    if(error == 1)
        emit shaderSceneStatus("vertex error");
    else if(error == 2)
        emit shaderSceneStatus("fragment error");
    else
        emit shaderSceneStatus("successfull");
    ShaderUse(_shader);
    ShaderSendf(_shader,"QuadParam" ,.0f,.0f,1.f,1.f);
    ShaderSendi(_shader,"texture0" ,0);
    ShaderSendi(_shader,"texture1" ,1);
    ShaderSendi(_shader,"texture2" ,2);
    ShaderSendi(_shader,"texture3" ,3);
    ShaderUse(0);

}
void RenderWidget::CompilPostShader(QString vertexShader, QString fragmentShader)
{
    int error;
    _postShader = ShaderCompil(vertexShader.toStdString().c_str(),fragmentShader.toStdString().c_str(),&error);
    if(error == 1)
        emit shaderPostStatus("vertex error");
    else if(error == 2)
        emit shaderPostStatus("fragment error");
    else
        emit shaderPostStatus("successfull");
    ShaderUse(_postShader);
    ShaderSendf(_postShader,"QuadParam" ,.0f,.0f,1.f,1.f);
    ShaderSendi(_postShader,"texture0" ,1);
    ShaderSendi(_postShader,"texture1" ,2);
    ShaderSendi(_postShader,"texture2" ,3);
    ShaderSendi(_postShader,"texture3" ,4);
    ShaderSendi(_postShader,"tex" ,0);
    ShaderUse(0);
}

void RenderWidget::resizeGL(int w, int h) {
    _w = w;
    _h = h;
    if(_h == 0) _h = 1;

    if (h == 0)	h = 1;
    glViewport(0, 0, _w, _h);

}
void RenderWidget::ApplyCamera()
{

    switch(_cameraType)
    {
    case 0:
    {
        if(_trigY < 0.005f) _trigY = 0.005f;
        if(_trigY > 3.14f)  _trigY = 3.14f;
        float px = sin(_trigX)*_zoomFactor*sin(_trigY)+_ofsX;
        float py = cos(_trigY)*_zoomFactor+_ofsY;
        float pz = cos(_trigX)*_zoomFactor*sin(_trigY)+_ofsZ;

        gluLookAt(px, py, pz, _ofsX, _ofsY, _ofsZ, 0.0f, 1.0f, 0.0f);
        break;
    }
    case 1:
        gluLookAt(_scene.camPosX, _scene.camPosY, _scene.camPosZ, _scene.camTargetX, _scene.camTargetY, _scene.camTargetZ, 0.0f, 1.0f, 0.0f);
        break;
    case 2:
    {
        float dTime = float(_freeflyTimer.elapsed()) / 2000.0f;
        _freeflyTimer.restart();
        _freeflyTrigX += dTime * _freeflyPxOffset;
        _freeflyTrigY -= dTime * _freeflyPyOffset;

        if(_freeflyTrigY < 0.005f) _freeflyTrigY = 0.005f;
        if(_freeflyTrigY > 3.14f)  _freeflyTrigY = 3.14f;
        float tx = sin(_freeflyTrigX)*sin(_freeflyTrigY)+_freeflyPosition.x();
        float ty = cos(_freeflyTrigY)+_freeflyPosition.y();
        float tz = cos(_freeflyTrigX)*sin(_freeflyTrigY)+_freeflyPosition.z();

        float dx = (tx-_freeflyPosition.x());
        float dy = (ty-_freeflyPosition.y());
        float dz = (tz-_freeflyPosition.z());

        float vNorm = sqrt(dx*dx+dy*dy+dz*dz);

        _freeflyPosition.setX(_freeflyPosition.x()+dx*_freeflySpeed/vNorm);
        _freeflyPosition.setY(_freeflyPosition.y()+dy*_freeflySpeed/vNorm);
        _freeflyPosition.setZ(_freeflyPosition.z()+dz*_freeflySpeed/vNorm);

        gluLookAt(_freeflyPosition.x(), _freeflyPosition.y(), _freeflyPosition.z(), tx, ty, tz, 0.0f, 1.0f, 0.0f);
        break;
    }
    default:
    {
        if(_trigY < 0.005f) _trigY = 0.005f;
        if(_trigY > 3.14f)  _trigY = 3.14f;
        float px = sin(_trigX)*_zoomFactor*sin(_trigY)+_ofsX;
        float py = cos(_trigY)*_zoomFactor+_ofsY;
        float pz = cos(_trigX)*_zoomFactor*sin(_trigY)+_ofsZ;

        gluLookAt(px, py, pz, _ofsX, _ofsY, _ofsZ, 0.0f, 1.0f, 0.0f);
        break;
    }
    }
}

void RenderWidget::paintGL() {
    QTime time;
    time.start();
    _fbo->Enable();

        glLoadIdentity();

        ApplyCamera();

        ShaderUse(_shader);

        ShaderSendf(_shader,"fov" ,_scene.fov);
        ShaderSendf(_shader,"time" ,((float)QTime::currentTime().elapsed())*.001f);
        for(int i=0; i<8; i++)
        {
            QString name = "Param[" + QString::number(i) + "]";
            ShaderSendf(_shader,name.toStdString().c_str() ,_scene.ShaderParam[i]);
        }
        for(int i=0; i<4; i++)
        {
            glActiveTexture(GL_TEXTURE0+i);
            glEnable(GL_TEXTURE_2D);
            glBindTexture(GL_TEXTURE_2D, _texture[i]);
        }

        Fast2DQuadDraw();
        ShaderUse(0);

    _fbo->Disable();

    glLoadIdentity();
    ApplyCamera();
    glViewport(0, 0, _w, _h);
    ShaderUse(_postShader);
        glActiveTexture(GL_TEXTURE0);
        glEnable(GL_TEXTURE_2D);
        glBindTexture(GL_TEXTURE_2D, _fbo->GetColor());
        for(int i=0; i<4; i++)
        {
            glActiveTexture(GL_TEXTURE1+i);
            glEnable(GL_TEXTURE_2D);
            glBindTexture(GL_TEXTURE_2D, _texture[i]);
        }
        ShaderSendf(_postShader,"time" , ((float)QTime::currentTime().elapsed())*.001f);
        for(int i=0; i<8; i++)
        {
            QString name = "Param[" + QString::number(i) + "]";
            ShaderSendf(_postShader,name.toStdString().c_str() ,_scene.PostParam[i]);
        }

        Fast2DQuadDraw();
        glBindTexture(GL_TEXTURE0,0);
    ShaderUse(0);

    glFlush();
    glFinish();

    //Compute Frame Per Second ;)
    float ms = float(time.elapsed())*0.001f;
    QString res;
    res.setNum (1.0/ms, 'f');
    emit setFPSLabel( res );


    update();



}


void RenderWidget::renderToBuffer(unsigned char *Data, int X,int Y)
{
    int sizeX = _fbo->GetSizeX(),
        sizeY = _fbo->GetSizeY(),
        camera = _cameraType;
    _fbo->SetSize(X,Y);
    setCamera(1);

    _fbo->Enable();

        glClear(GL_COLOR_BUFFER_BIT);
        glLoadIdentity();

        ApplyCamera();

        ShaderUse(_shader);

        ShaderSendf(_shader,"fov" ,_scene.fov);
        ShaderSendf(_shader,"time" ,((float)QTime::currentTime().elapsed())*.001f);
        for(int i=0; i<8; i++)
        {
            QString name = "Param[" + QString::number(i) + "]";
            ShaderSendf(_shader,name.toStdString().c_str() ,_scene.ShaderParam[i]);
        }
        for(int i=0; i<4; i++)
        {
            glActiveTexture(GL_TEXTURE0+i);
            glBindTexture(GL_TEXTURE_2D, _texture[i]);
        }
        for(float x=-0.9f; x<1.0f; x+=0.2f)
        for(float y=-0.9f; y<1.0f; y+=0.2f)
        {
            ShaderSendf(_shader,"QuadParam" ,x,y, .1f,.1f);
            Fast2DQuadDraw();
            glFlush();
        }
        ShaderSendf(_shader,"QuadParam" ,.0f,.0f,1.f,1.f);

        ShaderUse(0);

    _fbo->Disable();

    glLoadIdentity();
    ApplyCamera();
    glViewport(0, 0, _w, _h);
    ShaderUse(_postShader);
        ShaderSendf(_postShader,"time" , ((float)QTime::currentTime().elapsed())*.001f);
        for(int i=0; i<8; i++)
        {
            QString name = "Param[" + QString::number(i) + "]";
            ShaderSendf(_postShader,name.toStdString().c_str() ,_scene.PostParam[i]);
        }
        glActiveTexture(GL_TEXTURE0);
        glEnable(GL_TEXTURE_2D);
        glBindTexture(GL_TEXTURE_2D, _fbo->GetColor());
        for(int i=0; i<4; i++)
        {
            glActiveTexture(GL_TEXTURE1+i);
            glBindTexture(GL_TEXTURE_2D, _texture[i]);
        }
        Fast2DQuadDraw();
        glBindTexture(GL_TEXTURE0,0);
    ShaderUse(0);

    glFlush();
    glFinish();

    glReadPixels(0,0, X, Y, GL_RGB, GL_UNSIGNED_BYTE, Data);

    _fbo->SetSize(sizeX,sizeY);
    setCamera(camera);


}

void RenderWidget::mousePressEvent(QMouseEvent* event) {
    if(hasMouseTracking())
    {
        _shiftPressed = false;
        _freeflyEnabled = false;
        _freeflyPxOffset = _freeflyPyOffset = _freeflySpeed = 0.0f;
        setMouseTracking(false);
        QWidget::mousePressEvent(event);
        return;
    }

    _startMousePos = event->pos();
    _pressedButton = event->button();
    if(_shiftPressed) _moveToChangeOffset = true;
    QWidget::mousePressEvent(event);
}

void RenderWidget::mouseReleaseEvent(QMouseEvent* event) {
    _moveToChangeOffset = false;
    QWidget::mouseReleaseEvent(event);
}

void RenderWidget::mouseMoveEvent(QMouseEvent* event) {
    if(_freeflyEnabled)
    {
        float epx = event->pos().x();
        _freeflyPxOffset = (float(width() / 2) - epx) / float(width() / 2);
        float epy = event->pos().y();
        _freeflyPyOffset = (float(height() / 2) - epy) / float(height() / 2);


        QWidget::mouseMoveEvent(event);
        return;
    }

    switch(_pressedButton) {
    case Qt::MidButton:
        if(_moveToChangeOffset) {
            addViewOffset(0.0025f * float(event->pos().x() - _startMousePos.x()), 0.0025f * float(event->pos().y() - _startMousePos.y()));
        } else {
            _trigX -= 0.00125f * float(event->pos().x() - _startMousePos.x());
            _trigY -= 0.00125f * float(event->pos().y() - _startMousePos.y());
        }

        _startMousePos = event->pos();
        break;
    default:
        break;
    }

    QWidget::mouseMoveEvent(event);
}

void RenderWidget::wheelEvent(QWheelEvent* event) {
    if(_freeflyEnabled)
    {
        if(event->delta() > 0) _freeflySpeed += 0.005f;
          else _freeflySpeed -= 0.005f;

        QWidget::wheelEvent(event);
        return;
    }
    if(event->delta() > 0) _zoomFactor /= 1.025f;
      else _zoomFactor *= 1.025f;

    QWidget::wheelEvent(event);
}

void RenderWidget::setViewAngle(float x, float y)
{
    _trigX = x; _trigY = y;
}

void RenderWidget::addViewAngle(float x, float y)
{
    _trigX += x; _trigY += y;
}

void RenderWidget::addZoomFactor(float f)
{
    _zoomFactor += f;
}

void RenderWidget::setViewOffset(float x, float y, float z)
{
    _ofsX = x; _ofsY = y; _ofsZ = z;
}

void RenderWidget::addViewOffset(float x, float y)
{
    _ofsX += x * -cos(_trigX); _ofsY += y; _ofsZ += x * sin(_trigX);
}
void RenderWidget::setShiftPressed(bool b)
{
    _shiftPressed = b;
}

void RenderWidget::setDemoTime(float t)
{
    _demoTime = t;
}

void RenderWidget::setDemoTime(unsigned int t)
{
    setDemoTime(float(t)/1000.0f);
}

void RenderWidget::setSizeRTT(int width, int height)
{
    _fbo->SetSize(width,height);
}

void RenderWidget::setCamera(int a)
{
    _cameraType = a;
}

void RenderWidget::emitMakeWaypoint()
{
    switch(_cameraType)
    {
    case 0:
    {
        float px = sin(_trigX)*_zoomFactor*sin(_trigY)+_ofsX;
        float py = cos(_trigY)*_zoomFactor+_ofsY;
        float pz = cos(_trigX)*_zoomFactor*sin(_trigY)+_ofsZ;
        emit makeWaypoint(px, py, pz, _ofsX, _ofsY, _ofsZ);
        break;
    }
    case 2:
    {
        float tx = sin(_freeflyTrigX)*sin(_freeflyTrigY)+_freeflyPosition.x();
        float ty = cos(_freeflyTrigY)+_freeflyPosition.y();
        float tz = cos(_freeflyTrigX)*sin(_freeflyTrigY)+_freeflyPosition.z();

        emit makeWaypoint(_freeflyPosition.x(), _freeflyPosition.y(), _freeflyPosition.z(), tx, ty, tz);
        break;
    }
    }
}

void RenderWidget::startCameraFreefly()
{
    if(_cameraType == 2)
    {
        _freeflyEnabled = true;
        setMouseTracking(true);
        _freeflyTimer.restart();
        _shiftPressed = false;
    }
}
